package fs

import (
	"bufio"
	"github.com/sirupsen/logrus"
	"mydocker/common"
	"os"
	"path"
	"strings"
)

// 挂载点路径的索引
/*
42 29 0:38 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,memory
上述字符串按空格分割成切片后，index=4 的元素即 /sys/fs/cgroup/memory 就是我们要的挂载点路径
*/
const mountPointIndex = 4

// getCgroupPath 获取 cgroup 在文件系统中的绝对路径
/*
实际就是将根目录和 cgroup 名称拼接成一个路径。
如果指定了自动创建，就先检查一下是否存在，如果对应的目录不存在，则说明 cgroup 不存在，就创建一个。
因为 Cgroup V1 允许多个层次结构（hierarchies），每个层次结构可以启用不同的控制器。
所以还需要 subsystem 参数来帮助我们找出挂载了某个 subsystem 的 hierarchy cgroup 根节点所在的目录
*/
func getCgroupPath(subsystem string, cgroupPath string, autoCreate bool) (string, error) {
	cgroupRootPath, err := findCgroupMountPoint(subsystem)
	logrus.Infof("Cgroup Root Path: %s", cgroupRootPath)
	if err != nil {
		logrus.Errorf("find cgroup mount point, err: %v", err.Error())
		return "", err
	}
	// cgroup 绝对路径
	absPath := path.Join(cgroupRootPath, cgroupPath)
	if !autoCreate { // 不需要自动创建时就直接返回
		return absPath, nil
	}
	// 判断文件是否已经存在
	_, err = os.Stat(absPath)
	if err != nil && os.IsNotExist(err) {
		if err = os.MkdirAll(absPath, common.Perm0755); err != nil {
			return "", err
		}
	}
	return absPath, nil
}

// findCgroupMountPoint 通过 /proc/self/mountinfo 文件找出挂载了某个 subsystem 的 hierarchy cgroup 根节点所在的目录
func findCgroupMountPoint(subsystem string) (string, error) {
	// /proc/self/mountinfo 为linux系统中默认的存储当前进程的 mountinfo 信息的文件
	// 可以直接通过 cat /proc/self/mountinfo 命令查看
	f, err := os.Open("/proc/self/mountinfo") // 打开挂载信息文件
	if err != nil {                           // 如果打开文件失败，返回错误
		return "", err
	}
	defer f.Close()

	// 这里主要是根据各种字符串处理来找到目标位置
	scanner := bufio.NewScanner(f) // 创建 Scanner 对象，用于逐行读取文件内容
	for scanner.Scan() {           // 逐行扫描文件
		// txt 大概是这样的：42 29 0:38 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,memory
		txt := scanner.Text() // 获取当前行的文本
		// 按照空格分割
		fields := strings.Split(txt, " ")
		// 对最后一个元素按逗号进行分割，这里的最后一个元素就是 rw,memory
		// 其中的 memory 就表示这是一个 memory subsystem
		subsystems := strings.Split(fields[len(fields)-1], ",")
		for _, opt := range subsystems {
			if opt == subsystem && len(fields) > mountPointIndex {
				return fields[mountPointIndex], nil // 返回挂载点路径
			}
		}
	}

	return "", scanner.Err()
}
